﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SpiceNet.Circuits;
using SpiceNet.Diagnostics;
using SpiceNet.Simulations;
using SpiceNet.Parameters;

namespace SpiceNet.Models
{
    /// <summary>
    /// A model for diodes
    /// </summary>
    public class DiodeModel : Model
    {
        /// <summary>
        /// Get parameters
        /// </summary>
        public override Dictionary<string, IP> ParamTable => _pTable;

        /// <summary>
        /// Conductance corresponding to ohmic R
        /// </summary>
        public double DIOconductance { get; private set; }
        public double DIOf2 { get; private set; }
        public double DIOf3 { get; private set; }

        #region Parameters
        private enum _c
        {
            DIO_MOD_IS = 101,
            DIO_MOD_RS = 102,
            DIO_MOD_N = 103,
            DIO_MOD_TT = 104,
            DIO_MOD_CJO = 105,
            DIO_MOD_VJ = 106,
            DIO_MOD_M = 107,
            DIO_MOD_EG = 108,
            DIO_MOD_XTI = 109,
            DIO_MOD_FC = 110,
            DIO_MOD_BV = 111,
            DIO_MOD_IBV = 112,
            DIO_MOD_D = 113,
            DIO_MOD_COND = 114,
            DIO_MOD_TNOM = 115,
            DIO_MOD_KF = 116,
            DIO_MOD_AF = 117
        }
        private static Dictionary<string, IP> _pTable = new Dictionary<string, IP>
        {
            { "is", new IP(IF.IOP, (int)_c.DIO_MOD_IS, T.REAL, "Saturation current") },
            { "tnom", new IP(IF.IOPU, (int)_c.DIO_MOD_TNOM, T.REAL, "Parameter measurement temperature") },
            { "rs", new IP(IF.IOP, (int)_c.DIO_MOD_RS, T.REAL, "Ohmic resistance") },
            { "n", new IP(IF.IOP, (int)_c.DIO_MOD_N, T.REAL, "Emission Coefficient") },
            { "tt", new IP(IF.IOPA, (int)_c.DIO_MOD_TT, T.REAL, "Transit Time") },
            { "cjo", new IP(IF.IOPA, (int)_c.DIO_MOD_CJO, T.REAL, "Junction capacitance") },
            { "cj0", new IP(IF.IOPR, (int)_c.DIO_MOD_CJO, T.REAL, "Junction capacitance") },
            { "vj", new IP(IF.IOP, (int)_c.DIO_MOD_VJ, T.REAL, "Junction potential") },
            { "m", new IP(IF.IOP, (int)_c.DIO_MOD_M, T.REAL, "Grading coefficient") },
            { "eg", new IP(IF.IOP, (int)_c.DIO_MOD_EG, T.REAL, "Activation energy") },
            { "xti", new IP(IF.IOP, (int)_c.DIO_MOD_XTI, T.REAL, "Saturation current temperature exp.") },
            { "kf", new IP(IF.IOP, (int)_c.DIO_MOD_KF, T.REAL, "flicker noise coefficient") },
            { "af", new IP(IF.IOP, (int)_c.DIO_MOD_AF, T.REAL, "flicker noise exponent") },
            { "fc", new IP(IF.IOP, (int)_c.DIO_MOD_FC, T.REAL, "Forward bias junction fit parameter") },
            { "bv", new IP(IF.IOP, (int)_c.DIO_MOD_BV, T.REAL, "Reverse breakdown voltage") },
            { "ibv", new IP(IF.IOP, (int)_c.DIO_MOD_IBV, T.REAL, "Current at reverse breakdown voltage") },
            { "cond", new IP(IF.OPU, (int)_c.DIO_MOD_COND, T.REAL, "Ohmic conductance") },
            { "d", new IP(IF.IP, (int)_c.DIO_MOD_D, T.FLAG, "Diode model") }
        };

        public Parameter<double> DIOsatCur { get; } = new Parameter<double>(1.0e-14);
        public Parameter<double> DIOnomTemp { get; } = new Parameter<double>(300.15);
        public Parameter<double> DIOresist { get; } = new Parameter<double>();
        public Parameter<double> DIOemissionCoeff { get; } = new Parameter<double>(1.0);
        public Parameter<double> DIOtransitTime { get; } = new Parameter<double>();
        public Parameter<double> DIOjunctionCap { get; } = new Parameter<double>();
        public Parameter<double> DIOjunctionPot { get; } = new Parameter<double>(1.0);
        public Parameter<double> DIOgradingCoeff { get; } = new Parameter<double>(0.5);
        public Parameter<double> DIOactivationEnergy { get; } = new Parameter<double>(1.11);
        public Parameter<double> DIOsaturationCurrentExp { get; } = new Parameter<double>(3.0);
        public Parameter<double> DIOfNcoef { get; } = new Parameter<double>();
        public Parameter<double> DIOfNexp { get; } = new Parameter<double>(1.0);
        public Parameter<double> DIOdepletionCapCoeff { get; } = new Parameter<double>(0.5);
        public Parameter<double> DIObreakdownVoltage { get; } = new Parameter<double>();
        public Parameter<double> DIObreakdownCurrent { get; } = new Parameter<double>(1e-3);
        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name"></param>
        public DiodeModel(string name) : base(name) { }

        /// <summary>
        /// Set parameter
        /// </summary>
        protected override void Param(int id, object value, Circuit ckt = null)
        {
            switch ((_c)id)
            {
                case _c.DIO_MOD_IS:
                    DIOsatCur.Par((double)value);
                    break;
                case _c.DIO_MOD_TNOM:
                    DIOnomTemp.Par((double)value + Circuit.CONSTCtoK);
                    break;
                case _c.DIO_MOD_RS:
                    DIOresist.Par((double)value);
                    break;
                case _c.DIO_MOD_N:
                    DIOemissionCoeff.Par((double)value);
                    break;
                case _c.DIO_MOD_TT:
                    DIOtransitTime.Par((double)value);
                    break;
                case _c.DIO_MOD_CJO:
                    DIOjunctionCap.Par((double)value);
                    break;
                case _c.DIO_MOD_VJ:
                    DIOjunctionPot.Par((double)value);
                    break;
                case _c.DIO_MOD_M:
                    DIOgradingCoeff.Par((double)value);
                    break;
                case _c.DIO_MOD_EG:
                    DIOactivationEnergy.Par((double)value);
                    break;
                case _c.DIO_MOD_XTI:
                    DIOsaturationCurrentExp.Par((double)value);
                    break;
                case _c.DIO_MOD_FC:
                    DIOdepletionCapCoeff.Par((double)value);
                    break;
                case _c.DIO_MOD_BV:
                    DIObreakdownVoltage.Par((double)value);
                    break;
                case _c.DIO_MOD_IBV:
                    DIObreakdownCurrent.Par((double)value);
                    break;
                case _c.DIO_MOD_D:
                    /* no action - we already know we are a diode, but this */
                    /* makes life easier for spice-2 like parsers */
                    break;
                case _c.DIO_MOD_KF:
                    DIOfNcoef.Par((double)value);
                    break;
                case _c.DIO_MOD_AF:
                    DIOfNexp.Par((double)value);
                    break;
                default:
                    throw new BadParameterException();
            }
        }

        /// <summary>
        /// Ask parameter
        /// </summary>
        protected override object Ask(int id, Circuit ckt = null)
        {
            switch ((_c)id)
            {
                case _c.DIO_MOD_IS:
                    return DIOsatCur.Value;
                case _c.DIO_MOD_TNOM:
                    return DIOnomTemp - Circuit.CONSTCtoK;
                case _c.DIO_MOD_RS:
                    return DIOresist.Value;
                case _c.DIO_MOD_N:
                    return DIOemissionCoeff.Value;
                case _c.DIO_MOD_TT:
                    return DIOtransitTime.Value;
                case _c.DIO_MOD_CJO:
                    return DIOjunctionCap.Value;
                case _c.DIO_MOD_VJ:
                    return DIOjunctionPot.Value;
                case _c.DIO_MOD_M:
                    return DIOgradingCoeff.Value;
                case _c.DIO_MOD_EG:
                    return DIOactivationEnergy.Value;
                case _c.DIO_MOD_XTI:
                    return DIOsaturationCurrentExp.Value;
                case _c.DIO_MOD_FC:
                    return DIOdepletionCapCoeff.Value;
                case _c.DIO_MOD_KF:
                    return DIOfNcoef.Value;
                case _c.DIO_MOD_AF:
                    return DIOfNexp.Value;
                case _c.DIO_MOD_BV:
                    return DIObreakdownVoltage.Value;
                case _c.DIO_MOD_IBV:
                    return DIObreakdownCurrent.Value;
                case _c.DIO_MOD_COND:
                    return DIOconductance;
                default:
                    throw new BadParameterException();
            }
        }
        
        /// <summary>
        /// Diode model temperature parameters
        /// </summary>
        public class DIOTempParameters
        {
            public double xfc, vtnom;
        }

        /// <summary>
        /// Do temperature dependent stuff
        /// </summary>
        /// <param name="ckt"></param>
        public override void Temperature(Circuit ckt)
        {
            DIOTempParameters tp = new DIOTempParameters();

            if (!DIOnomTemp.Given)
                DIOnomTemp.Value = ckt.Config.NominalTemperature;
            tp.vtnom = Circuit.CONSTKoverQ * DIOnomTemp;
            /* limit grading coeff to max of .9 */
            if (DIOgradingCoeff > .9)
            {
                CircuitWarning.Warning(this, string.Format("Model {0}: Grading coefficient too large, limited to 0.9", Name));
                DIOgradingCoeff.Value = 0.9;
            }
            /* limit activation energy to min of .1 */
            if (DIOactivationEnergy < 0.1)
            {
                CircuitWarning.Warning(this, string.Format("Model {0}: Activation energy too small, limited to 0.1", Name));
                DIOactivationEnergy.Value = 0.1;
            }
            /* limit depletion cap coeff to max of .95 */
            if (DIOdepletionCapCoeff > .95)
            {
                CircuitWarning.Warning(this, string.Format("Model {0}: Coefficient Fc too large, limited to 0.95", Name));
                DIOdepletionCapCoeff.Value = 0.95;
            }
            if (!DIOresist.Given || DIOresist == 0)
            {
                DIOconductance = 0;
            }
            else
            {
                DIOconductance = 1.0 / DIOresist;
            }
            tp.xfc = Math.Log(1.0 - DIOdepletionCapCoeff);

            // Do temperature stuff for instances
            foreach (var inst in Instances)
                inst.Temperature(ckt, this, tp);

            DIOf2 = Math.Exp((1.0 + DIOgradingCoeff) * tp.xfc);
            DIOf3 = 1.0 - DIOdepletionCapCoeff *
                    (1.0 + DIOgradingCoeff);
        }

        /// <summary>
        /// Load noise contributions
        /// </summary>
        /// <param name="ckt"></param>
        /// <param name="mode"></param>
        /// <param name="operation"></param>
        /// <param name="data"></param>
        /// <param name="outNdens"></param>
        public override void NoiseLoad(Circuit ckt,
            Noise.OutputMode mode,
            Noise.Operation operation,
            NoiseData data,
            ref double outNdens)
        {
            foreach (var inst in Instances)
                inst.NoiseLoad(ckt, mode, operation, data, ref outNdens, this);
        }
    }
}
